package com.louis.ylog.util;

import cn.hutool.core.lang.ClassScaner;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.louis.ylog.advice.CustomerCatAdvice;
import com.louis.ylog.feign.CatConstantsExt;
import com.louis.ylog.filter.CatTransactionFilter;
import com.louis.ylog.starter.YLogProperties;
import com.louis.ylog.tiger.TigerAdvice;
import com.louis.ylog.tiger.TigerKey;
import com.louis.ylog.tiger.TigerLog;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.repository.Repository;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Slf4j
@Component
@ConditionalOnProperty(value = "ylog.enable-y-log", havingValue = "true")
public class AopImporter {

//    private static final String RocketMQConsumeAop  = "args(com.aliyun.openservices.ons.api.Message,com.aliyun.openservices.ons.api.ConsumeContext)";
    private static final String RocketMQConsumeAop  = "@within(com.louis.component.rocket.annotation.RocketMQMessageListener)";
    private static final String MapperAop  = "@within(org.apache.ibatis.annotations.Mapper)";
    private static final String FeignAop  = "@within(org.springframework.cloud.openfeign.FeignClient)";
    private static final String RedisAop  = "execution(* org.springframework.data.redis.core.RedisTemplate.*(..))";
    private static final String FakeBeanAop = "target(com.louis.ylog.util.FakeBean)";

    private static final String TigerAop  ="@annotation(com.louis.ylog.tiger.TigerLog)";

    private static final String PackageAopFormat  = "execution(public * %s..*.*(..))";

    public static final String CatAopValidExpression="${ylog.enable-y-log}&&${ylog.cat.enable-cat}&&${ylog.cat.enable-spring-bean-aop:true}";

    public static final String ElkEnableExpression="${ylog.enable-y-log}&&${ylog.elk.enable-elk}";

    @Autowired
    private YLogProperties logProperties;

    @Bean
    @ConditionalOnProperty(value = "ylog.enable-y-log", havingValue = "true")
    public FilterRegistrationBean catTransactionFilter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        CatTransactionFilter filter = new CatTransactionFilter();
        registration.setFilter(filter);
        registration.addUrlPatterns("/*");
        registration.setName("catTransaction-filter");
        registration.setOrder(-9999);
        return registration;
    }

    @Bean
    @ConditionalOnExpression(CatAopValidExpression)
    public AspectJExpressionPointcutAdvisor customerCatAdvice() {
        String customAop=logProperties.getCat().getSpringBeanAopPointcut();
        if(StringUtils.isEmpty(customAop)){
            customAop=FakeBeanAop;
        }
        AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
        advisor.setExpression(customAop);
        advisor.setAdvice(new CustomerCatAdvice(CatConstantsExt.Type_Spring_Bean_Method));
        log.info("ymlPointCut:{}",logProperties.getCat().getSpringBeanAopPointcut());
        return advisor;
    }

    @Bean
    @ConditionalOnExpression(CatAopValidExpression)
    @ConditionalOnClass(FeignClient.class)
    public AspectJExpressionPointcutAdvisor customerFeignCatAdvice() {
        String pointCut=FeignAop;
        AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
        advisor.setExpression(pointCut);
        advisor.setAdvice(new CustomerCatAdvice(CatConstantsExt.Type_Feign_Client));
        log.info("Type_Feign_Client_AOP:{}",pointCut);
        return advisor;
    }

    @Bean
    @ConditionalOnExpression(CatAopValidExpression)
    @ConditionalOnClass(RedisTemplate.class)
    public AspectJExpressionPointcutAdvisor customerRedisCatAdvice() {
        String pointCut=RedisAop;
        AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
        advisor.setExpression(pointCut);
        advisor.setAdvice(new CustomerCatAdvice(CatConstantsExt.Type_RedisTemplate_OP));
        log.info("Type_RedisTemplate_OP_AOP:{}",pointCut);
        return advisor;
    }

    @Bean
    @ConditionalOnExpression(AopImporter.ElkEnableExpression + "&&" + AopImporter.CatAopValidExpression)
    @ConditionalOnClass(TigerLog.class)
    public AspectJExpressionPointcutAdvisor tigerCatAdvice() {
        String pointCut=TigerAop;
        Map<Class,List<Field>> tigerClass=getTigerKeyFieldMap();
        if(CollectionUtils.isEmpty(tigerClass)){
            pointCut=FakeBeanAop;
        }else {
            TigerAdvice.CachedTigerMethodFields.putAll(tigerClass);
        }
        AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
        advisor.setExpression(pointCut);
        advisor.setAdvice(new TigerAdvice());
        log.info("Type_Tiger_Action_AOP:{}",pointCut);
        return advisor;
    }

//    @Bean
//    @ConditionalOnExpression(CatAopValidExpression)
//    @ConditionalOnClass(com.liveyu.component.rocket.annotation.RocketMQMessageListener.class)
//    public AspectJExpressionPointcutAdvisor customerRocketMQConsumeCatAdvice() {
//        String pointCut=RocketMQConsumeAop;
//        AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
//        advisor.setExpression(pointCut);
//        advisor.setAdvice(new CustomerCatAdvice(CatConstantsExt.Type_RocketMQ_Consume));
//        log.info("Type_RocketMQ_Consume_AOP:{}",pointCut);
//        return advisor;
//    }

    @Bean
    @ConditionalOnExpression(CatAopValidExpression)
    @ConditionalOnClass(MapperScan.class)
    public AspectJExpressionPointcutAdvisor customerMyBatisCatAdvice() {
        String mybatisPointCut=getMybatisAopPointCut();
        if(StringUtils.isEmpty(mybatisPointCut)){
            mybatisPointCut=MapperAop;
        }else {
            mybatisPointCut=String.format("%s || (%s)",MapperAop,mybatisPointCut);
        }
        AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
        advisor.setExpression(mybatisPointCut);
        advisor.setAdvice(new CustomerCatAdvice(CatConstantsExt.Type_MyBatis_Mapper));
        log.info("Type_MyBatis_Mapper:{}",mybatisPointCut);
        return advisor;
    }

    @Bean
    @ConditionalOnExpression(CatAopValidExpression)
    @ConditionalOnClass(Repository.class)
    public AspectJExpressionPointcutAdvisor customerJpaCatAdvice() {
        String jpaPointCut=getJpaAopPointCut();
        if(StringUtils.isEmpty(jpaPointCut)){
            jpaPointCut=FakeBeanAop;
        }
        AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
        advisor.setExpression(jpaPointCut);
        advisor.setAdvice(new CustomerCatAdvice(CatConstantsExt.Type_Spring_Jpa_Query));
        log.info("Type_Spring_Jpa_Query:{}",jpaPointCut);
        return advisor;
    }







    private static final ConcurrentHashMap<String,Set<Class<?>>> packageClass=new ConcurrentHashMap<>();

    private Set<Class<?>> getAllPackageClass(){
        String packageToScan="com.liveyu";
        try {
            if(!packageClass.containsKey(packageToScan)){
                packageClass.put(packageToScan,ClassScaner.scanPackage(packageToScan));
            }
            return packageClass.get(packageToScan);
        }catch (Exception ex){
            log.error("获取{}包下所有类异常",packageToScan,ex);
            return Sets.newHashSet();
        }
    }

    /**
     * 获取@MapperScan注解使用的mybatis类
     * @return
     */
    private String getMybatisAopPointCut(){
        try {
            Set<Class<?>> allPackageClass =  getAllPackageClass();
            if(CollectionUtils.isEmpty(allPackageClass)){
                return "";
            }
            Set<String> mapperPackages=  new HashSet<>();
            for (Class packageClass : allPackageClass){
                MapperScan mapperScan= (MapperScan) packageClass.getAnnotation(MapperScan.class);
                if(mapperScan==null){
                    continue;
                }
                String[] basePackage=mapperScan.basePackages();
                String[] valuesPackage=mapperScan.value();
                Class[] baseClass = mapperScan.basePackageClasses();
                if(basePackage!=null&&basePackage.length>0){
                    mapperPackages.addAll(Arrays.stream(basePackage).collect(Collectors.toList()));
                }
                if(valuesPackage!=null&&valuesPackage.length>0){
                    mapperPackages.addAll(Arrays.stream(valuesPackage).collect(Collectors.toList()));
                }
                if(baseClass!=null&&baseClass.length>0){
                    mapperPackages.addAll(Arrays.stream(baseClass).map(c->c.getPackage().getName()).collect(Collectors.toList()));
                }
            }
            if(CollectionUtils.isEmpty(mapperPackages)){
                return "";
            }
            return mapperPackages.stream().map(s->String.format(PackageAopFormat,s)).collect(Collectors.joining(" || "));
        }catch (Exception ex){
            log.error("获取MybatisAopPointCut异常",ex);
            return "";
        }
    }


    /**
     * 获取注解了TigerKey的类
     * @return
     */
    private Map<Class, List<Field>> getTigerKeyFieldMap(){
        try {
            Set<Class<?>> allPackageClass =  getAllPackageClass();
            if(CollectionUtils.isEmpty(allPackageClass)){
                return Maps.newHashMap();
            }
            Map<Class, List<Field>> result=new HashMap<>();

            for (Class packageClass : allPackageClass){
                if(packageClass.isInterface()){
                    continue;
                }
                List<Field> tigerFields=getClassTigerFields(packageClass);
                if(!CollectionUtils.isEmpty(tigerFields)){
                    result.put(packageClass,tigerFields);
                }

                //内部类
                Class[] innerClasses = packageClass.getDeclaredClasses();
                if(innerClasses!=null&&innerClasses.length>0){
                    for(Class innerClass:innerClasses){
                        List<Field> innerTigerFields=getClassTigerFields(innerClass);
                        if(!CollectionUtils.isEmpty(innerTigerFields)){
                            result.put(innerClass,innerTigerFields);
                        }
                    }
                }
            }

            return result;
        }catch (Exception ex){
            log.error("获取getTigerKeyFieldMap异常",ex);
            return Maps.newHashMap();
        }
    }

    private  List<Field> getClassTigerFields(Class packageClass){
        List<Field> tigerFields=new ArrayList<>();
        for (Field field:packageClass.getDeclaredFields()){
            TigerKey tigerKey = field.getAnnotation(TigerKey.class);
            if(tigerKey!=null){
                field.setAccessible(true);
                tigerFields.add(field);
            }
        }
        return tigerFields;
    }

    /**
     * 获取Jpa类 (集成Repository接口的类)
     * @return
     */
    private String getJpaAopPointCut(){
        try {
            Set<Class<?>> allPackageClass =  getAllPackageClass();
            if(CollectionUtils.isEmpty(allPackageClass)){
                return "";
            }
            Set<String> jpaPackages=  new HashSet<>();

            for (Class packageClass : allPackageClass){
                if(packageClass.isInterface()&& isJpaRepository(packageClass)){
                    jpaPackages.add(packageClass.getPackage().getName());
                    continue;
                }
            }

            return jpaPackages.stream().map(s->String.format(PackageAopFormat,s)).collect(Collectors.joining(" || "));
        }catch (Exception ex){
            log.error("获取JpaAopPointCut异常",ex);
            return "";
        }
    }

    /**
     * 动态规划， 空间换时间
     */
    private static final ConcurrentHashMap<String,Class> JpaRepository = new ConcurrentHashMap<>();

    private boolean isJpaRepository(Class beanClass){
        if(JpaRepository.containsKey(beanClass.getName())){
            return true;
        }
        if(beanClass.equals(Repository.class)){
            JpaRepository.put(beanClass.getName(),beanClass);
            return true;
        }
        if(beanClass.getInterfaces()==null||beanClass.getInterfaces().length==0){
            return false;
        }
        for (Class superInterface : beanClass.getInterfaces()){
            if(isJpaRepository(superInterface)){
                JpaRepository.put(beanClass.getName(),beanClass);
                return true;
            }
        }
        return false;
    }

}
